package com.hk.core.data.interceptors;

import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.hibernate.CallbackException;
import org.hibernate.Interceptor;
import org.hibernate.annotations.DynamicUpdate;
import org.hibernate.type.Type;
import org.springframework.lang.Nullable;

import java.util.ArrayList;


/**
 * Hibernate 自定义拦截器，默认情况下，在执行更新操作时，会更新所有新对象的属性，即使这个属性值为 null 也会更新，
 * 当实体类上有了 {@link DynamicUpdate} 注解时，会比较新对象的DB中的对象，只要属性的值不相同，就会更新。
 * <p>
 * 此拦截器是在执行更新操作时，只更新新对象不为空的属性，不管数据库中的属性是否有值
 * <p>
 * 注意: 此拦截器可以配置在Hibernate sessionFactory中， 也可以配置在每一个session中，配置在 SessionFactory中时要特别注意线程安全。
 * <a href="https://hibernate.net.cn/docs/65.html">官方文档地址</a>
 *
 * <pre>
 *
 * 结合Spring data jpa 配置:
 * 在 application.yaml 中添加:
 * ---------------------------------------如果要配置在 每一个 Session 中: -------------------------------------------------------
 * spring:
 *    jpa:
 *       properties:
 *         hibernate:
 *           session_factory:
 *             session_scoped_interceptor: com.hk.core.data.interceptors.DynamicUpdateInterceptor
 *
 * ---------------------------------------如果要配置在 SessionFactory中: -------------------------------------------------------
 *    spring:
 *     jpa:
 *        properties:
 *          hibernate:
 *            session_factory:
 *              interceptor: com.hk.core.data.interceptors.DynamicUpdateInterceptor
 * </pre>
 *
 * @author Kevin
 */
@NoArgsConstructor(access = AccessLevel.PUBLIC)
public class DynamicUpdateInterceptor implements Interceptor {

    @Override
    public boolean onFlushDirty(Object entity, Object id, Object[] currentState,
                                Object[] previousState, String[] propertyNames, Type[] types) throws CallbackException {
        return entity.getClass().isAnnotationPresent(DynamicUpdate.class);
    }

    @Override
    @Nullable
    public int[] findDirty(Object entity, Object id, Object[] currentState,
                           Object[] previousState, String[] propertyNames, Type[] types) {
        /*
            当实体类上标注了使用 DynamicUpdate 时，
            在更新操作时，Hibernate 会根据id查询表中的记录，并比较DB中查询的实体和要更新的实体属性，只要有属性的值和DB不一样，就会加入到更新的字段中(及时要更新的实体属性为null)
                如 Db 查询的对象为 {"id":12345,"name": "username","email":"username@xx.com","sex":"男"}
                要更新的对象为 {"id":12345,"name": "username-update","email": null,"sex":null}
                此时，要更新的对象 email属性为null, hibernate默认也会更新 email 属性，并设置为 null ，最后保存到DB的结果是:
                    {"id":12345,"name": "username-update","email": null,"sex":"男"}
               可知: 要更新的email值为null，把原来表中已存在的 email 值也设置为null了。

           此方法就是保证 更新的对象都不为空的属性。即不会更新 email，最后保存的结果是:
                {"id":12345,"name": "username-update","email":"username@xx.com","sex":"男"}

         */
        if (entity.getClass().isAnnotationPresent(DynamicUpdate.class)) {
            var list = new ArrayList<Integer>();
            for (int i = 0, len = currentState.length; i < len; i++) {
                if (currentState[i] != null) {
                    list.add(i);
                }
            }
            return list.stream().mapToInt(Integer::intValue).toArray();
        }
        return null;
    }
}
